define(async() => {
    const util = await require("libs/util");

    const Scene = (() => {

        const
            name = Symbol("name"),
            script = Symbol("script");
        
        class Scene {
    
            constructor() {
    
            }
            
        }

        return Scene;
    })();


    const Frame = (() => {

        const
            backgroundColor = Symbol("backgroundColor"),
            layers = Symbol("layers");

        
        class Frame {

            constructor(data) {
                util.instanceInject(this, data, [
                    [false, "backgroundColor", "#000"],
                    [false, "layers", []]
                ]);
            }

            render(ctx) {
                this[layers].forEach(layer => {
                    layer.render(ctx);
                });
                return this;
            }

            prepare() {

                return this;
            }

            set backgroundColor(value) {
                util.assert(util.isHexColor(value), "param must be hex color");
                this[backgroundColor] = value;
            }

            get backgroundColor() {
                return this[backgroundColor];
            }

            set layers(value) {
                util.assert(Frame.isLayers(value), "param must be Layer[]");
                this[layers] = value;
            }

            get layers() {
                return this[layers];
            }

        }
        Frame.isLayers = value => util.isArray(value) && value.filter(o => o instanceof Layer).length === value.length;

        return Frame;
    })();

    const Layer = (() => {

        const 
            width = Symbol("width"),
            height = Symbol("height"),
            backgroundColor = Symbol("backgroundColor"),
            index = Symbol("index"),
            blocks = Symbol("blocks");

        class Layer {

            constructor(data) {
                util.instanceInject(this, data, [
                    [true, "width"],
                    [true, "height"],
                    [false, "backgroundColor"],
                    [false, "index", 0],
                    [false, "blocks", []]
                ]);
            }

            render(ctx) {
                this[blocks].forEach(block => block.render(ctx));
            }

            fill(shape) {
                if(!(shape instanceof Shape))
                    throw new TypeError("shape type must be Shape");
                const block = new LayerBlock({
                    width: this[width],
                    height: this[height],
                    material: shape,
                    repeat: true
                });
                this[blocks].push(block);
            }

            set width(value) {
                util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                this[width] = value;
            }

            get width() {
                return this[width];
            }

            set height(value) {
                util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                this[height] = value;
            }

            get height() {
                return this[height];
            }

            set backgroundColor(value) {
                util.assert(util.isHexColor(value), "param must be hex color");
                this[backgroundColor] = value;
            }

            get backgroundColor() {
                return this[backgroundColor];
            }

            set index(value) {
                util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                this[index] = value;
            }

            get index() {
                return this[index];
            }

            set blocks(value) {
                util.assert(Layer.isBlocks(value), "param must be LayerBlock[]");
                this[blocks] = value;
            }

            get blocks() {
                return this[blocks];
            }

        }
        Layer.isBlocks = value => util.isArray(value) && value.filter(o => o instanceof LayerBlock).length === value.length;

        const LayerBlock = (() => {

            const
                x = Symbol("x"),
                y = Symbol("y"),
                width = Symbol("width"),
                height = Symbol("height"),
                material = Symbol("material"),
                repeat = Symbol("repeat"),
                backgroundColor = Symbol("backgroundColor");
    
            class LayerBlock {
    
                constructor(data) {
                    util.instanceInject(this, data, [
                        [false, "x", 0],
                        [false, "y", 0],
                        [false, "width", 100],
                        [false, "height", 100],
                        [false, "material"],
                        [false, "repeat", false],
                        [false, "backgroundColor", "#000"]
                    ]);
                }
    
                render(ctx) {
                    if(this[repeat]) {
                        for(let _x = 0;_x < Math.ceil(this[width] / (this[material].position ? this[material].position.width : this[material].width));_x++) {
                            for(let _y = 0;_y < Math.ceil(this[height] / (this[material].position ? this[material].position.height : this[material].height));_y++) {
                                // console.log(this[material].position.x, this[material].position.y, this[material].position.width, this[material].position.height, this[x] + _x * this[material].position.width, this[y] + _y * this[material].position.height, this[material].position.width, this[material].position.height)
                                if(this[material].position) 
                                    ctx.drawImage(this[material].image, this[material].position.x, this[material].position.y, this[material].position.width, this[material].position.height, this[x] + _x * this[material].position.width, this[y] + _y * this[material].position.height, this[material].position.width, this[material].position.height);
                                else
                                    ctx.drawImage(this[material].image, this[x] + _x * this[material].width, this[y] + _y * this[material].height, this[material].width, this[material].height);
                            }
                        }
                    }
                    else {
                        if(this[material].position) {
                            // console.log(this[material].position.x, this[material].position.y, this[material].position.width, this[material].position.height);
                            ctx.drawImage(this[material].image, this[material].position.x, this[material].position.y, this[material].position.width, this[material].position.height, this[x], this[y], this[material].position.width, this[material].position.height);
                        }
                        else
                            ctx.drawImage(this[material].image, this[x], this[y], this[material].width, this[material].height);
                    }
                }
    
                set x(value) {
                    util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                    this[x] = value;
                }
    
                get x() {
                    return this[x];
                }
                
                set y(value) {
                    util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                    this[y] = value;
                }
    
                get y() {
                    return this[y];
                }
    
                set width(value) {
                    util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                    this[width] = value;
                }
    
                get width() {
                    return this[width];
                }
    
                set height(value) {
                    util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                    this[height] = value;
                }
    
                get height() {
                    return this[height];
                }
    
                set material(value) {
                    util.assert(util.isObject(value), TYPE_ERROR.OBJECT);
                    this[material] = value;
                }
    
                get material() {
                    return this[material];
                }
    
                set repeat(value) {
                    util.assert(util.isBoolean(value), TYPE_ERROR.BOOLEAN);
                    this[repeat] = value;
                }
    
                get repeat() {
                    return this[repeat];
                }
    
                set backgroundColor(value) {
                    util.assert(util.isHexColor(value), "param must be hex color");
                    this[backgroundColor] = value;
                }
    
                get backgroundColor() {
                    return this[backgroundColor];
                }
    
            }
    
            return LayerBlock;
        })();

        Layer.Block = LayerBlock;
        return Layer;
    })();

    const Shape = (() => {

        class Shape {

            constructor(data) {
                
            }

        }

        const ImageShape = (() => {

            const
                image = Symbol("image"),
                position = Symbol("position");
    
            class ImageShape extends Shape {
        
                constructor(data) {
                    super();
                    util.instanceInject(this, data, [
                        [true, "image"],
                        [false, "position"]
                    ]);
                }
        
                set image(value) {
                    util.assert(value instanceof Image, "param type must be Image");
                    this[image] = value;
                }
        
                get image() {
                    return this[image];
                }

                set position(value) {
                    util.assert(value instanceof ImageShapePosition, "param must be ImageShapePosition");
                    this[position] = value;
                }

                get position() {
                    return this[position];
                }
        
                get width() {
                    return this[image].width;
                }
        
                get height() {
                    return this[image].height;
                }
        
            }
    
            const ImageShapePosition = (() => {
    
                const
                    x = Symbol("x"),
                    y = Symbol("y"),
                    width = Symbol("width"),
                    height = Symbol("height");
    
                class ImageShapePosition {
    
                    constructor(data) {
                        util.instanceInject(this, data, [
                            [true, "x"],
                            [true, "y"],
                            [true, "width"],
                            [true, "height"]
                        ]);
                    }
    
                    set x(value) {
                        util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                        this[x] = value;
                    }
        
                    get x() {
                        return this[x];
                    }
                    
                    set y(value) {
                        util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                        this[y] = value;
                    }
        
                    get y() {
                        return this[y];
                    }
        
                    set width(value) {
                        util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                        this[width] = value;
                    }
        
                    get width() {
                        return this[width];
                    }
        
                    set height(value) {
                        util.assert(util.isNumber(value), TYPE_ERROR.NUMBER);
                        this[height] = value;
                    }
        
                    get height() {
                        return this[height];
                    }
    
                }
    
                return ImageShapePosition;
            })();
            ImageShape.Position = ImageShapePosition;
    
            return ImageShape;
        })();
        Shape.ImageShape = ImageShape;

        return Shape;
    })();

    return {
        Scene,
        Frame,
        Layer,
        Shape
    };
});